package com.xherorlclass.loadbalancer.impl;

import com.xherorlclass.XherorlRpcBootstrap;
import com.xherorlclass.loadbalancer.AbstractLoadBalancer;
import com.xherorlclass.loadbalancer.Selector;
import com.xherorlclass.transport.message.XherorlRpcRequest;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author ：Xherorl
 * @date ：Created in 2023/8/29 9:32
 * @description：一致性hash的负载均衡策略
 */
@Slf4j
public class ConsistentHashBalancer extends AbstractLoadBalancer {
    @Override
    protected Selector getSelector(List<InetSocketAddress> serviceList) {
        return new ConsistentHashSelector(serviceList, 128);
    }

    /**
     * 一致性hash的具体算法实现
     */
    private static class ConsistentHashSelector implements Selector{

        // hash环用来存储服务节点
        private SortedMap<Integer, InetSocketAddress> circle = new TreeMap<>();

        // 虚拟节点的个数
        private int virtualNodes;

        public ConsistentHashSelector(List<InetSocketAddress> serviceList, int virtualNodes){
            // 我们应该尝试将节点转化为虚拟节点，进行挂载
            this.virtualNodes = virtualNodes;
            for (InetSocketAddress inetSocketAddress : serviceList) {
                // 需要把每一个节点加入到hash环中
                addNodeToCircle(inetSocketAddress);
            }
        }

        /**
         * 将每个节点挂载到hash环上
         * @param inetSocketAddress 节点的地址
         */
        private void addNodeToCircle(InetSocketAddress inetSocketAddress) {
            // 为每一个节点生成匹配的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash = hash(inetSocketAddress.toString() + "-" + i);
                // 挂载到hash环上
                circle.put(hash,inetSocketAddress);
                if (log.isDebugEnabled()){
                    log.debug("hash为【{}】的节点已经挂载到了哈希环上。", hash);
                }
            }
        }

        /**
         * 具体的hash算法, todo 小小的遗憾，这样也是不均匀的
         * @param s
         * @return
         */
        private int hash(String s) {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("MD5");
            }catch (NoSuchAlgorithmException e){
                throw new RuntimeException(e);
            }
            byte[] digest = md.digest(s.getBytes());
            // md5得到的结果是一个字节数组，但是我们想要int 4个字节

            int res = 0;
            for (int i = 0; i < 4; i++) {
                res = res << 8;
                if (digest[i] < 0){
                    // 说明是负数
                    res = res | (digest[i] & 255);
                }else {
                    res = res | digest[i];
                }
            }
            return res;
        }


        @Override
        public InetSocketAddress getNext() {

            // 1、hash环已经建立好了，接下来需要对请求的要素做处理我们应该选择什么要素来进行hash运算
            // 有没有办法可以获取到具体的请求内容  --> threadLocal

            /*
             * 在Java中，ThreadLocal类提供了一种线程级别的变量隔离机制，可以用于以下几个方面：

             * 线程上下文传递：通过ThreadLocal，可以在多个方法或组件之间方便地共享数据，而无需显式地传递参数。每个线程都可以独立地访问自己的ThreadLocal变量副本，从而避免了线程安全问题。

             * 线程相关数据存储：ThreadLocal可用于存储和访问与线程相关的数据。例如，在Web应用程序中，可以使用ThreadLocal保存当前登录用户的信息，以便在整个请求处理过程中轻松地访问。

             * 线程级别的单例模式：通过ThreadLocal，可以实现线程级别的单例模式。即每个线程都拥有自己的实例副本，可以避免多线程环境下的竞争条件，并且不需要使用同步机制。

             * 线程上下文切换：在多线程环境中，线程之间的上下文切换可能会导致性能下降。使用ThreadLocal可以减少线程之间的上下文切换频率，提高系统性能。
             * */

            XherorlRpcRequest xherorlRpcRequest = XherorlRpcBootstrap.REQUEST_THREAD_LOCAL.get();

            // 根据请求的一些特征来选择服务器  id
            String requestId = Long.toString(xherorlRpcRequest.getRequestId());

            // 请求的id做hash，字符串默认的hash不太好
            int hash = hash(requestId);

            // 判断该hash值是否能直接落在一个服务器上，和服务器的hash一样
            if (!circle.containsKey(hash)){
                // 寻找离我最近的一个节点
                SortedMap<Integer, InetSocketAddress> tailMap = circle.tailMap(hash);

                hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
            }
            return circle.get(hash);
        }

        private void removeNodeFromCircle(InetSocketAddress inetSocketAddress) {
            // 为每一个节点生成匹配的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash = hash(inetSocketAddress.toString() + "-" + i);
                circle.remove(hash);
            }
        }

    }
}
